package com.fengwk.support.pndao;

import com.fengwk.support.pndao.DaoGen.Named;
import com.fengwk.support.util.ArrayUtils;
import com.fengwk.support.util.CollectionUtils;
import com.fengwk.support.util.ObjectUtils;
import com.fengwk.support.util.StringUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.util.Context;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.annotations.Param;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.lang.model.element.ElementKind;
import javax.lang.model.type.MirroredTypeException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DaoGenHelper {

	private Types types;
	@SuppressWarnings("unused")
	private Trees trees;

	public DaoGenHelper(Trees trees, Context context) {
		this.trees = trees;
		this.types = Types.instance(context);
	}

	public static Document parseText(String text) throws DocumentException, SAXException {
		SAXReader reader = new SAXReader(false);
		reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
		reader.setFeature("http://apache.org/xml/features/validation/schema", false);
		String encoding = getEncoding(text);

		InputSource source = new InputSource(new StringReader(text));
		source.setEncoding(encoding);

		Document result = reader.read(source);

		// if the XML parser doesn't provide a way to retrieve the encoding,
		// specify it manually
		if (result.getXMLEncoding() == null) {
			result.setXMLEncoding(encoding);
		}

		return result;
	}

	private static String getEncoding(String text) {
		String result = null;

		String xml = text.trim();

		if (xml.startsWith("<?xml")) {
			int end = xml.indexOf("?>");
			String sub = xml.substring(0, end);
			StringTokenizer tokens = new StringTokenizer(sub, " =\"\'");

			while (tokens.hasMoreTokens()) {
				String token = tokens.nextToken();

				if ("encoding".equals(token)) {
					if (tokens.hasMoreTokens()) {
						result = tokens.nextToken();
					}

					break;
				}
			}
		}

		return result;
	}
	
	public String mixMethodToData(DaoGen daoGen, String namespace, Map<String, MapperMethod> methodMap, String data, DaoEnv daoEnv) {
		if (data.isEmpty()) {
			data = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "\n" 
					+ "<!DOCTYPE mapper\n"
					+ "PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n"
					+ "\n" 
					+ "<mapper namespace=\"" + namespace + "\">\n" 
					+ "</mapper>\n";
		}
		try {
			Class<?> sqlColsClass = null;
			try {
				sqlColsClass = daoGen.sqlCols();
			} catch (MirroredTypeException e) {
				String classValue = e.getTypeMirror().toString();
				if (StringUtils.isNotBlank(classValue)) {
					try {
						sqlColsClass = Class.forName(classValue);
					} catch (ClassNotFoundException e1) {
						e1.printStackTrace();
					}
				}
			}
			boolean createSqlCols = sqlColsClass != null && sqlColsClass != Void.class;
			boolean createSqlColsAlias = createSqlCols;
			
			Document document = parseText(data);
			Element element = document.getRootElement();
			for (Element sqlEle: element.elements()) {
				String id = sqlEle.attribute("id").getText();
				methodMap.remove(id);
				
				if (ObjectUtils.equals(id, daoGen.sqlColsName())) {
					createSqlCols = false;
				}
				if (ObjectUtils.equals(id, daoGen.sqlColsAliasName())) {
					createSqlColsAlias = false;
				}
			}
			
			// 建立cols
			if (createSqlCols) {
				buildCols(daoGen, element, sqlColsClass, daoEnv, false);
			}
			if (createSqlColsAlias) {
				buildCols(daoGen, element, sqlColsClass, daoEnv, true);
			}
			
			methodMap.forEach(getGenFunc(daoGen, element));
			
			OutputFormat format = OutputFormat.createPrettyPrint();
			try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
				XMLWriter writer = new XMLWriter(outputStream, format);
				writer.write(document);
				writer.flush();

				return outputStream.toString("utf-8");
			}
		} catch (DocumentException | IOException | SAXException e) {
			throw new Error(e);
		}
	}

	private BiConsumer<String, MapperMethod> getGenFunc(DaoGen daoGen, Element root) {
		return (key, method) -> {
			if (!(handledWithThisPrefix(key, daoGen::insertPrefix, addInsert(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::updateForPrefix, addUpdateFor(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::updatePrefix, addUpdate(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::findPrefix, addQueryOrFind(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::queryAllPrefix, addQueryAll(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::queryInPrefix, addQueryIn(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::queryPrefix, addQueryOrFind(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::countAllPrefix, addCountAll(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::countPrefix, addCountBy(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::countInPrefix, addCountIn(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::removePrefix, addRemove(daoGen, key, method, root))
					|| handledWithThisPrefix(key, daoGen::batchInsertPrefix, addBatchInsert(daoGen, key, method, root)))) {
				throw new Error("unknown method to be auto gen:" + key);
			}
		};
	}

	private Consumer<String> addCountIn(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("select");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);
			sql.addAttribute("resultType", "int");
			String left = key.replaceFirst(prefix, "");
			List<String> params = split(daoGen, left);
			StringBuilder select = new StringBuilder(50);
			select.append("select count(*) from  ").append(method.getDaoEnv().getTableName());
			int len = params.size();
			if (len != 1)
				throw new Error("count in method only support one param");
			if (!params.isEmpty())
				select.append(" where ");
			String param = params.get(0);
			param = getFirstParamValue(method, param);
			
			sql.addText(select.toString());
			if (param.endsWith("s"))
				param = lowerFirst(param.substring(0, param.length() - 1));

			Element choose = sql.addElement("choose");
			String collection = formatParamNamed(daoGen, param).replaceAll("s$", "") + "s";
			Element when = choose.addElement("when");
			when.addAttribute("test", collection + " !=null and " + collection + ".size() > 0");
			when.addText("`" + formatDbNamed(daoGen, collection.replaceAll("s$", "")) + "` in ");

			Element each = when.addElement("foreach");
			each.addAttribute("item", "item");
			each.addAttribute("collection", collection);
			each.addAttribute("open", "(");
			each.addAttribute("separator", ",");
			each.addAttribute("close", ")");
			each.addText("#{item}");

			Element otherwise = choose.addElement("otherwise");
			otherwise.addText(" 1 = 2 ");
		};
	}

	private Consumer<String> addUpdateFor(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("update");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);

			StringBuilder updateSql = new StringBuilder(50);
			updateSql.append("update ").append(method.getDaoEnv().getTableName()).append(" set \n");

			String left = key.replaceFirst(prefix, "");
			// 支持By
			List<String> rights = null;
			int lastBy = left.lastIndexOf("By");
			if (lastBy != -1) {
				String right = left.substring(lastBy);
				right = right.replace("By", "");
				left = left.substring(0, lastBy);
				rights = split(daoGen, right);
			}
			
			// 支持In
			int lastIn = left.lastIndexOf("In");
			String in = null;
			if (lastIn != -1) {
				String right = left.substring(lastIn);
				in = right.replace("In", "");
				left = left.substring(0, lastIn);
			}
			List<String> fields = split(daoGen, left).stream().map(this::lowerFirst).collect(Collectors.toList());
			fields.add(method.getDaoEnv().getUpdateTime());
			String pk = getPrimaryKey(daoGen);
			updateSql.append(Joiner.on(", ")
					.join(fields.stream()
							.filter((field -> !field.equals(pk) && !method.getDaoEnv().getCreateTime().equals(field)))
							.map((field -> {
								if (method.getDaoEnv().getUpdateTime().equals(field))
									return "`" + field + "` = " + "now() ";
								else
									return "`" + field + "` = " + "#{" + formatParamNamed(daoGen, field) + "} ";
							})).iterator()));

			updateSql.append("where ");
			String whereSql = null;
			if (CollectionUtils.isBlank(rights)) {
				if (in == null)
					whereSql = buildWhereParamSql(daoGen, pk);
			} else {
				whereSql = buildWhereSql(daoGen, rights);
			}
			if (whereSql != null) {
				updateSql.append(whereSql);
			}
			sql.addText(updateSql.toString());
			if (in != null) {
				// In
				Element choose = sql.addElement("choose");
				Element when = choose.addElement("when");
				String collection = formatParamNamed(daoGen, in).replaceAll("s$", "") + "s";
				when.addAttribute("test", collection + " !=null and " + collection + ".size() > 0");
				when.addText("`" + formatDbNamed(daoGen, collection.replaceAll("s$", "")) + "` in ");
				Element each = when.addElement("foreach");
				each.addAttribute("item", "item");
				each.addAttribute("collection", collection);
				each.addAttribute("open", "(");
				each.addAttribute("separator", ",");
				each.addAttribute("close", ")");
				each.addText("#{item}");
				Element otherwise = choose.addElement("otherwise");
				otherwise.addText(" 1 = 2 ");
			}
		};
	}

	private Consumer<String> addQueryIn(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("select");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);
			sql.addAttribute("resultType", method.getReturnType().toString());
			String left = key.replaceFirst(prefix, "");
			String orderClause = "";
			boolean shouldOrder = false;
			if (left.contains(daoGen.orderBy())) {
				shouldOrder = true;
				int index = left.indexOf(daoGen.orderBy());
				orderClause = left.substring(index + daoGen.orderBy().length(), left.length());
				left = left.substring(0, index);
			}
			List<String> params = split(daoGen, left);
			StringBuilder select = new StringBuilder(50);
			List<String> fields = getFields(daoGen, method.getReturnType());
			select.append("select ").append(Joiner.on(", ").join(fields.stream().map(f -> getSelectField(daoGen, f)).iterator()))
					.append(" from ").append(method.getDaoEnv().getTableName());
			int len = params.size();
			if (len != 1)
				throw new Error("query in method only support one param");
			if (!params.isEmpty())
				select.append(" where ");
			String param = params.get(0);
			// 首个@param参数优先
			param = getFirstParamValue(method, param);
			
			sql.addText(select.toString());
			if (param.endsWith("s"))
				param = lowerFirst(param.substring(0, param.length() - 1));

			Element choose = sql.addElement("choose");
			String collection = formatParamNamed(daoGen, param).replaceAll("s$", "") + "s";
			Element when = choose.addElement("when");
			when.addAttribute("test", collection + " !=null and " + collection + ".size() > 0");
			when.addText("`" + formatDbNamed(daoGen, collection.replaceAll("s$", "")) + "` in ");

			Element each = when.addElement("foreach");
			each.addAttribute("item", "item");
			each.addAttribute("collection", collection);
			each.addAttribute("open", "(");
			each.addAttribute("separator", ",");
			each.addAttribute("close", ")");
			each.addText("#{item}");

			Element otherwise = choose.addElement("otherwise");
			otherwise.addText(" 1 = 2 ");

			if (shouldOrder) {
				select = new StringBuilder();
				String order = daoGen.defaultOrder();
				String orderKey = getPrimaryKey(daoGen);
				if (!orderClause.isEmpty()) {
					if (orderClause.contains(daoGen.orderByWith())) {
						int index = orderClause.indexOf(daoGen.orderByWith());
						order = orderClause.substring(index + daoGen.orderByWith().length(), orderClause.length());
						orderKey = orderClause.substring(0, index);
					} else {
						orderKey = orderClause;
					}
				}
				select.append(" order by ").append(formatDbNamed(daoGen, orderKey)).append(" ").append(order.toLowerCase());
				sql.addText(select.toString());
			}
		};
	}

	private Consumer<String> addRemove(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("delete");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);
			String left = key.replaceFirst(prefix, "");
			List<String> params = split(daoGen, left);
			if (params.isEmpty())
				throw new Error("Remove method needs at least  one param");
			StringBuilder select = new StringBuilder(50);
			select.append("delete from ").append(method.getDaoEnv().getTableName());
			int len = params.size();
			if (!params.isEmpty())
				select.append(" where ");
			int cur = 0;
			appendParams(daoGen, params, select, len, cur);
			sql.addText(select.toString());
		};
	}

	private void appendParams(DaoGen daoGen, List<String> params, StringBuilder select, int len, int cur) {
		for (String param : params) {
			cur++;
			String realParam = lowerFirst(param);
			select.append("`").append(realParam).append("`").append(" = ").append("#{")
					.append(formatParamNamed(daoGen, realParam)).append("}");
			if (cur < len)
				select.append(" and ");
		}
	}

	private boolean handledWithThisPrefix(String key, Supplier<String[]> prefixs, Consumer<String> genWithPrefix) {
		for (String prefix : prefixs.get()) {
			if (key.startsWith(prefix)) {
				genWithPrefix.accept(prefix);
				return true;
			}
		}
		return false;
	}

	public <T extends Symbol> List<T> getMember(Class<T> type, ElementKind kind, Symbol classSymbol) {
		List<T> results = Lists.newArrayList();
		if (classSymbol.type == null || classSymbol.type.isPrimitiveOrVoid()) {
			return results;
		}
		for (Type t : types.closure(classSymbol.type)) {
			Scope scope = t.tsym.members();
			if (scope == null)
				continue;
			scope.getElements(symbol -> symbol.getKind() == kind).forEach(s -> results.add(type.cast(s)));
		}
		if (classSymbol.owner != null && classSymbol != classSymbol.owner
				&& classSymbol.owner instanceof Symbol.ClassSymbol) {
			results.addAll(getMember(type, kind, classSymbol.owner));
		}
		if (classSymbol.type.getEnclosingType() != null && classSymbol.hasOuterInstance()) {
			results.addAll(getMember(type, kind, classSymbol.type.getEnclosingType().asElement()));
		}
		return results;
	}

	private String lowerFirst(String word) {
		return Character.toLowerCase(word.charAt(0)) + word.substring(1);
	}

	private static final String COMMENT = "added by pn-plugin";

	private Consumer<String> addCountAll(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return addCount(daoGen, key, method, root, (params) -> {
		});
	}

	private Consumer<String> addCountBy(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return addCount(daoGen, key, method, root, (params) -> {
			if (params.isEmpty())
				throw new Error("At least need one param");
		});
	}

	private Consumer<String> addCount(DaoGen daoGen, String key, MapperMethod method, Element root,
			Consumer<List<String>> validator) {
		return (prefix) -> {
			Element sql = root.addElement("select");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);
			sql.addAttribute("resultType", "int");
			String left = key.replaceFirst(prefix, "");
			List<String> params = split(daoGen, left);
			validator.accept(params);
			StringBuilder select = new StringBuilder(50);
			select.append("select count(*) from ").append(method.getDaoEnv().getTableName());
			int len = params.size();
			if (!params.isEmpty())
				select.append(" where ");
			int cur = 0;
			appendParams(daoGen, params, select, len, cur);
			sql.addText(select.toString());
		};
	}

	private Consumer<String> addQueryAll(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return addQuery(daoGen, key, method, root, (params) -> {
		});
	}

	private Consumer<String> addQuery(DaoGen daoGen, String key, MapperMethod method, Element root,
			Consumer<List<String>> validator) {
		return (prefix) -> {
			Element sql = root.addElement("select");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);
			sql.addAttribute("resultType", method.getReturnType().toString());
			String left = key.replaceFirst(prefix, "");
			String orderClause = "";
			boolean shouldOrder = false;
			if (left.contains(daoGen.orderBy())) {
				shouldOrder = true;
				int index = left.indexOf(daoGen.orderBy());
				orderClause = left.substring(index + daoGen.orderBy().length(), left.length());
				left = left.substring(0, index);
			}
			List<String> params = split(daoGen, left);
			validator.accept(params);
			StringBuilder select = new StringBuilder(50);
			List<String> fields = getFields(daoGen, method.getReturnType());
			select.append("select ").append(Joiner.on(", ").join(fields.stream().map(f -> getSelectField(daoGen, f)).iterator()))
					.append(" from ").append(method.getDaoEnv().getTableName());
			int len = params.size();
			if (!params.isEmpty())
				select.append(" where ");
			int cur = 0;
			appendParams(daoGen, params, select, len, cur);
			if (shouldOrder) {
				String order = daoGen.defaultOrder();
				String orderKey = getPrimaryKey(daoGen);
				if (!orderClause.isEmpty()) {
					if (orderClause.contains(daoGen.orderByWith())) {
						int index = orderClause.indexOf(daoGen.orderByWith());
						order = orderClause.substring(index + daoGen.orderByWith().length(), orderClause.length());
						orderKey = orderClause.substring(0, index);
					} else {
						orderKey = orderClause;
					}
				}
				select.append(" order by ").append(formatDbNamed(daoGen, orderKey)).append(" ").append(order.toLowerCase());
			}
			sql.addText(select.toString());
		};

	}

	private Consumer<String> addQueryOrFind(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return addQuery(daoGen, key, method, root, (params) -> {
			if (params.isEmpty())
				throw new Error("At least need one param");
		});
	}

	private Consumer<String> addUpdate(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("update");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);

			StringBuilder updateSql = new StringBuilder(50);
			updateSql.append("update ").append(method.getDaoEnv().getTableName()).append(" set \n");

			List<String> fields = getFields(daoGen, method.getFirstParamType());
			String pk = getPrimaryKey(daoGen);
			String updateByField;
			List<String> updateByFields = null;
			if (key.startsWith("updateBy")) {
				updateByField = formatDbNamed(daoGen, key.substring(8));
				updateByFields = split(daoGen, updateByField);
			} else {
				updateByField = pk;
			}
			updateSql
					.append(Joiner
							.on(", ").join(
									fields.stream()
											.filter((field -> !field.equals(pk) && !field.equals(updateByField)
													&& !method.getDaoEnv().getCreateTime().equals(field)))
											.map((field -> {
												if (method.getDaoEnv().getUpdateTime().equals(field))
													return "`" + field + "` = " + "now() ";
												else
													return "`" + field + "` = " + "#{" + formatParamNamed(daoGen, field)
															+ "} ";
											})).iterator()));
			
			updateSql.append("where ");
			String whereSql = null;
			if (CollectionUtils.isBlank(updateByFields)) {
				whereSql = buildWhereParamSql(daoGen, updateByField);
			} else {
				whereSql = buildWhereSql(daoGen, updateByFields);
			}
			updateSql.append(whereSql);
			sql.addText(updateSql.toString());
		};
	}

	private Stream<String> getInsertFieldsStream(DaoGen daoGen, String pk, List<String> fields) {
		return fields.stream().filter((field) -> !daoGen.autoIncrement() || !pk.equals(field));
	}

	private Consumer<String> addBatchInsert(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("insert");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);

			StringBuilder insertSql = new StringBuilder(50);
			insertSql.append("insert into ").append(method.getDaoEnv().getTableName()).append("\n");

			String pk = getPrimaryKey(daoGen);
			List<String> fields = getFields(daoGen, method.getFirstParamType());
			insertSql.append("(")
					.append(Joiner.on(", ").join(getInsertFieldsStream(daoGen, pk, fields).map(f -> "`" + f + "`").iterator()))
					.append(")\n");
			
			insertSql.append("values ");
			sql.addText(insertSql.toString());
			Element foreach = sql.addElement("foreach");
			foreach.addAttribute("collection", getFirstParamValue(method, "list"));
			foreach.addAttribute("item", "item");
			foreach.addAttribute("separator", ",");
			StringBuilder eachSql = new StringBuilder(50);
			eachSql.append("(").append(Joiner.on(", ").join(getInsertFieldsStream(daoGen, pk, fields).map(field -> {
				if (method.getDaoEnv().getCreateTime().equals(field) || method.getDaoEnv().getUpdateTime().equals(field))
					return "now()";
				else
					return "#{item." + formatParamNamed(daoGen, field) + "}";
			}).iterator()));
			eachSql.append(")");
			foreach.addText(eachSql.toString());
		};
	}

	private Consumer<String> addInsert(DaoGen daoGen, String key, MapperMethod method, Element root) {
		return (prefix) -> {
			Element sql = root.addElement("insert");
			sql.addComment(COMMENT);
			sql.addAttribute("id", key);

			StringBuilder insertSql = new StringBuilder(50);
			insertSql.append("insert into ").append(method.getDaoEnv().getTableName()).append("\n");
			
			String pk = getPrimaryKey(daoGen);
			List<String> fields = getFields(daoGen, method.getFirstParamType());
			insertSql.append("(")
					.append(Joiner.on(", ").join(getInsertFieldsStream(daoGen, pk, fields).map(f -> "`" + f + "`").iterator()))
					.append(")\n");

			insertSql.append("values (");
			insertSql.append(Joiner.on(", ").join(getInsertFieldsStream(daoGen, pk, fields).map(field -> {
				if (method.getDaoEnv().getCreateTime().equals(field)
						|| method.getDaoEnv().getUpdateTime().equals(field))
					return "now()";
				else
					return "#{" + formatParamNamed(daoGen, field) + "}";
			}).iterator()));
			insertSql.append(")");
			sql.addText(insertSql.toString());
			
			if(daoGen.autoIncrement()) {
				Element selectKey = sql.addElement("selectKey");
				selectKey.addAttribute("keyProperty", pk);
				selectKey.addAttribute("order", "AFTER");
				selectKey.addText("select @@IDENTITY as " + pk);
			}
			
		};
	}

	public static MapperMethod toMapperMethod(DaoEnv daoEnv, Symbol.MethodSymbol methodSymbol) {
		return new MapperMethod(daoEnv, methodSymbol);
	}

	public static String getMethodName(Symbol.MethodSymbol methodSymbol) {
		return methodSymbol.getSimpleName().toString();
	}

	private Map<String, List<String>> fieldsMap = Maps.newHashMap();

	// 提取返回model字段
	private List<String> getFields(DaoGen daoGen, Symbol.TypeSymbol type) {
		String typeStr = type.toString();
		if (!fieldsMap.containsKey(typeStr)) {
			List<Symbol.VarSymbol> varSymbols = getMember(Symbol.VarSymbol.class, ElementKind.FIELD, type);
			fieldsMap.put(typeStr, varSymbols.stream().filter(s -> !s.isStatic())
					.map(s -> formatDbNamed(daoGen, s.toString())).collect(Collectors.toList()));
		}
		return fieldsMap.get(typeStr);
	}

	private static String getSelectField(DaoGen daoGen, String f) {
		String paramF = formatParamNamed(daoGen, f);
		String dbF = formatDbNamed(daoGen, f);
		if(paramF.equals(dbF)) {
			return "`" + f + "`";
		} else {
			return "`" + dbF + "` as " + paramF;
		}
	}
	
	// 分割方法命名
	private static List<String> split(DaoGen daoGen, String left) {
		List<String> results = new ArrayList<>();
		if (left == null || left.isEmpty())
			return results;
		for (String str : left.split(daoGen.separator())) {
			if (str != null && !str.isEmpty())
				results.add(formatDbNamed(daoGen, str));
		}
		return results;
	}

	// 获取主键
	private static String getPrimaryKey(DaoGen daoGen) {
		String pk = daoGen.primaryKey();
		return formatDbNamed(daoGen, pk);
	}
	
	private void buildCols(DaoGen daoGen, Element root, Class<?> clazz, DaoEnv daoEnv, boolean alias) {
		Field[] files = FieldUtils.getAllFields(clazz);
		if (ArrayUtils.isNotBlank(files)) {
			Element sql = root.addElement("sql");
			sql.addComment(COMMENT);
			sql.addAttribute("id", alias ? daoGen.sqlColsAliasName() : daoGen.sqlColsName());
			StringBuffer sb = new StringBuffer();
			for (Field field: files) {
				int modifiers = field.getModifiers();
				if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isNative(modifiers)) {
					String fieldName = field.getName();
					String dbName = formatDbNamed(daoGen, fieldName);
					if (alias) {
						// 表别名
						String tableAlias = daoGen.tableAlias();
						if (StringUtils.isBlank(tableAlias)) {
							StringBuffer tableAliasSb = new StringBuffer();
							String tableName = daoEnv.getTableName();
							tableName = StringUtils.toLowerUnderlineCase(tableName);
							String[] tableNames = tableName.split(StringUtils.UNDERLINE);
							if (ArrayUtils.isNotBlank(tableNames)) {
								for (String tableNameItem: tableNames) {
									if (StringUtils.isNotEmpty(tableNameItem)) {
										tableAliasSb.append(tableNameItem.charAt(0));
									}
								}
							}
							tableAlias = tableAliasSb.toString();
						}
						sb.append(tableAlias).append(".");
					}
					sb.append("`").append(dbName).append("`");
					String paramName = formatParamNamed(daoGen, fieldName);
					if (!ObjectUtils.equals(dbName, paramName)) {
						sb.append(" as ").append(paramName);
					}
					sb.append(", ");
				}
			}
			if (sb.length() > 0) {
				sql.addText(sb.substring(0, sb.length() - 2) + " ");
			}
		}
	}
	
	private String buildWhereSql(DaoGen daoGen, List<String> params) {
		String whereSql = null;
		StringBuffer sb = new StringBuffer();
		for (String param: params) {
			sb.append(buildWhereParamSql(daoGen, param));
		}
		whereSql = sb.toString();
		whereSql = whereSql.replaceFirst("and", "");
		return whereSql;
	}
	
	private String buildWhereParamSql(DaoGen daoGen, String paramName) {
		StringBuffer sb = new StringBuffer();
		sb.append(" and `").append(formatDbNamed(daoGen, paramName)).append("` = ").append("#{").append(formatParamNamed(daoGen, paramName)).append("} ");
		return sb.toString();
	}
	
	private List<Param> listParamByMapperMethod(MapperMethod method) {
		List<Param> paramList = new ArrayList<Param>();
		List<VarSymbol> vsList = method.getParams();
		if (CollectionUtils.isNotBlank(vsList)) {
			vsList.forEach(param -> {
				Param p = param.getAnnotation(Param.class);
				if (p != null) {
					paramList.add(p);
				}
			});
		}
		return paramList;
	}
	
	private String getFirstParamValue(MapperMethod method, String defaultValue) {
		List<Param> paramList = listParamByMapperMethod(method);
		if (CollectionUtils.isNotBlank(paramList)) {
			return paramList.get(0).value();
		}
		return defaultValue;
	}
	
	public static String formatDbNamed(DaoGen daoGen, String dbParam) {
		Named named = daoGen.dbNamed();
		return formatNamed(named, dbParam);
	}

	public static String formatParamNamed(DaoGen daoGen, String param) {
		Named named = daoGen.paramNamed();
		return formatNamed(named, param);
	}

	public static String formatNamed(Named named, String str) {
		if (named == Named.CAMEL) {
			return StringUtils.toLowerCamelCase(str);
		}
		if (named == Named.UNDER_LINE) {
			return StringUtils.toLowerUnderlineCase(str);
		}
		return str;
	}
	
}
